/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Client
 *
 * listener.c: Implementierung des Listener-Threads
 */

#include "common/sockets.h"
#include "gui/gui_interface.h"
#include "listener.h"
#include "fragewechsel.h"
#include "gui_thread.h"
#include "main.h"

static int sock; //Socket

static int status = 0; //Spielstatus

/* Liefert den Spielstatus */
int get_game_status() { return status; }

/* Setzte den Spielstatus */
void set_game_status(int game)
{
	if (game == GAME_RUNNING)
	{
		status = GAME_RUNNING;
	}
	return;
}

/* Struktur mit den Spielerdaten */
typedef struct player_data
{
	char name[32]; /* Spielername */
	uint32_t score; /* Punktestand */
	uint8_t id; /* SpielerId	*/
} PLAYER_DATA;

PLAYER_DATA players[MAX_USERS]; // Spielerarray

/* Aktualisiert struct mit der Playerliste */
void set_player_data(struct RFC_MSG *server_msg)
{
	int i, offset = 0;

	for (i = 0; i < MAX_USERS; i++)
	{

		if (offset < server_msg->header.length)
		{

			memset(players[i].name, 0, 32);

			memcpy(players[i].name, server_msg->data + offset, 32);

			memcpy(&players[i].score, server_msg->data + offset + 32, 4);

			players[i].score = ntohl(players[i].score);

			memcpy(&players[i].id, server_msg->data + offset + 36, 1);

			offset = offset + 37;

		} else {

			strcpy(players[i].name, "");

			players[i].score = 0;

			players[i].id = 255;
		}
	}
}

/* GUI Vorbereitung Playerliste */
int prep_player_list()
{

	preparation_clearPlayers();

	int i;
	for (i = 0; i < MAX_USERS; i++)
	{

		if (players[i].id != 255)
		{
			debugPrint("%s", players[i].name);

			char name[36] = { 0 };

			strcpy(name, players[i].name);

			if (players[i].id == 0)
			{
				strcat(name, "(SL)");
			}

			preparation_addPlayer(name);
		}
	}

	return 0;
}

/* GUI Spielphase Playerliste */
int game_player_list()
{
	int position;

	for (position = 1; position <= MAX_USERS; position++)
	{
		if (players[position - 1].id == 255)
		{
			game_setPlayerName(position, NULL);

		} else {

			char name[36] = { 0 };

			strcpy(name, players[position - 1].name);

			if (players[position - 1].id == 0)
			{
				strcat(name, " (SL)");
			}

			game_setPlayerName(position, name);

			game_setPlayerScore(position, players[position - 1].score);

			if (players[position - 1].id == get_client_id())
			{
				game_highlightPlayer(position);
			}
		}

	}
	return 0;
}

/* GUI Ausgewahlten Katalog hervorheben */
int highlight_catalog(struct RFC_MSG *server_msg)
{
	// Kopiere Daten des Void-Pointer
	char* chosen_catalog = (char*) malloc(server_msg->header.length + 1);

	memset(chosen_catalog, 0, server_msg->header.length + 1);

	memcpy(chosen_catalog, server_msg->data, server_msg->header.length);

	// Hebe Katalog hervor
	if (preparation_selectCatalog(chosen_catalog) == 0)
	{
		debugPrint("Katalog nicht in Liste gefunden\n");

		return -1;
	}
	return 0;
}

/* GUI Katalog hinzufuegen */
int add_catalog(struct RFC_MSG *server_msg)
{

	// Kopiere Daten des Void-Pointer

	if (server_msg->header.length != 0)
	{
		char* catalog = (char*) malloc(server_msg->header.length + 1);

		memcpy(catalog, server_msg->data, server_msg->header.length);

		memset(catalog + server_msg->header.length, '\0', 1);

		// fügt Katalog hinzu
		preparation_addCatalog(catalog);
	}

	return 0;
}

/* Spiel starten */
int start_game(struct RFC_MSG *server_msg)
{
	preparation_hideWindow();

	game_setAnswerButtonsEnabled(0);

	game_setQuestion("Jetzt geht's los...");

	game_showWindow();

	game_setStatusText("Katalog wird geladen...");

	return 0;
}

/* GUI Frage anzeigen */
int new_question(struct RFC_MSG *server_msg)
{
	game_setAnswerButtonsEnabled(1);

	game_unmarkAnswers();

	QuestionMessage question;

	int offset = 0;

	memcpy(question.question, server_msg->data, QUESTION_SIZE);

	offset = offset + QUESTION_SIZE;

	int i;
	for (i = 0; i < 4; i++)
	{
		memcpy(&question.answers[i][0], server_msg->data + offset, ANSWER_SIZE);

		offset = offset + ANSWER_SIZE;
	}

	memcpy(&question.timeout, server_msg->data + offset,
			sizeof(question.timeout));

	game_setQuestion(question.question);

	for (i = 0; i < 4; i++)
	{
		game_setAnswer(i, &question.answers[i][0]);
	}

	char timeout[12];

	sprintf(timeout, "Zeit: %us", question.timeout);

	game_setStatusIcon(STATUS_ICON_NONE);

	game_setStatusText(timeout);

	return 0;
}

/* GUI Ergebnis anzeigen */
int show_result(struct RFC_MSG *server_msg)
{
	uint8_t timeout;
	uint8_t correct;

	memcpy(&timeout, server_msg->data, sizeof(timeout));

	memcpy(&correct, server_msg->data + sizeof(timeout), sizeof(correct));

	if (timeout != 0)
	{
		game_setAnswerButtonsEnabled(0);

		game_markAnswerCorrect(correct);

		game_setStatusIcon(STATUS_ICON_TIMEOUT);

		game_setStatusText("Die Zeit für die Frage ist abgelaufen!");

		return 0;
	}

	game_markAnswerCorrect(correct);

	int answer = get_answer();

	if (answer == correct)
	{
		game_setStatusIcon(STATUS_ICON_CORRECT);

		game_setStatusText("Antwort richtig!");

	} else {
		game_markAnswerWrong(answer);

		game_setStatusIcon(STATUS_ICON_WRONG);

		game_setStatusText("Antwort falsch!");
	}

	return 0;
}

/* GUI Anzeige wenn alle Fragen beantwortet wurden */
void wait_for_game_over()
{
	game_setQuestion("Du hast alle Fragen bearbeitet - warte auf andere Spieler");

	game_unmarkAnswers();

	game_setAnswerButtonsEnabled(0);

	int i;
	for (i = 0; i < 4; i++)
	{
		game_setAnswer(i, "");
	}

	game_setStatusIcon(STATUS_ICON_NONE);

	game_setStatusText("Warte auf Spielende...");

	return;
}

/* GUI Rang anzeigen */
int show_rank(struct RFC_MSG *server_msg)
{
	uint8_t rank = 0;

	memcpy(&rank, server_msg->data, sizeof(uint8_t));

	if (rank == 1)
	{
		infoPrint("Du bist Sieger!\n");

		guiShowMessageDialog("Du bist Sieger!", 1);

	} else {
		infoPrint("Du bist %d. !\n", rank);

		char rank_ch;

		sprintf(&rank_ch, "%d", rank);

		char rank_message[13] = { "Du bist ..!" };

		memcpy(rank_message + 8, &rank_ch, sizeof(char));

		guiShowMessageDialog(rank_message, 1);
	}

	return 0;
}

/* Startfunktion des Listener Threads */
void* listener_thread(void* param)
{
	sock = get_socket();

	// MSG-Struktur für Nachrichtenempfang
	struct RFC_MSG server_msg;

	uint8_t subtype = 0;

	char* err_msg;

	// While Schleife des Listener Thread
	// Wird verlassen bei schweren Fehlerfällen
	// und Ende des Spiels
	bool wait_for_dialog_exit = false;

	// Warte auf GUI Thread bis dieser GUI initialisiert hat
	sem_wait(&gui_thread_init);

	// Zerstöre Semaphor, da nicht mehr benötigt
	sem_destroy(&gui_thread_init);

	/*******While Schleife zum Empfangen von Nachrichten*******/

	while (1)
	{
		// Setze Message-Werte zurück
		server_msg.header.type = 0;
		server_msg.header.length = 0;

		/*******Reagiere auf empfangenen MSG-Typ*******/

		switch (catch_com_err(receive_MSG(sock, &server_msg)))
		{
			case CATALOG_RESPONSE:

				add_catalog(&server_msg);

				break;

			case CATALOG_CHANGE:

				highlight_catalog(&server_msg);

				break;

			case PLAYER_LIST:

				set_player_data(&server_msg);

				if (status == GAME_RUNNING)
				{
					game_player_list();

				} else {
					prep_player_list();
				}
				break;

			case START_GAME:

				start_game(&server_msg);

				set_game_status(GAME_RUNNING);

				sem_post(&question);

				break;

			case QUESTION:

				if (server_msg.header.length > 0)
				{
					new_question(&server_msg);

				} else {
					wait_for_game_over();
				}

				break;

			case QUESTION_RESULT:

				show_result(&server_msg);

				sem_post(&question);

				break;

			case GAME_OVER:

				show_rank(&server_msg);

				wait_for_dialog_exit = true;

				break;

			case ERROR_WARNING:

				// Holt Speicher für Error-Msg
				err_msg = (char*) malloc(server_msg.header.length + sizeof(char));

				// err_msg mit Nullen fuellen (automatische Ende-Markierung)
				memset(err_msg, 0, (server_msg.header.length + sizeof(char)));

				// Kopiert Subtyp und Fehlerbeschreibung aus Error-MSG
				memcpy(&subtype, server_msg.data, sizeof(uint8_t));
				memcpy(err_msg, server_msg.data + 1, server_msg.header.length
						- sizeof(uint8_t));

				switch (subtype)
				{
					case WARNING:

						// Ausgabe des Fehlers auf Konsole
						infoPrint("%s", err_msg);

						// Ausgabe des Fehlers in GUI
						guiShowMessageDialog(err_msg, 0);

						break;

					case FATAL:

						// Ausgabe des Fehlers auf Konsole
						infoPrint("%s\n", err_msg);

						// Ausgabe des Fehlers in GUI
						guiShowErrorDialog(err_msg, 1);

						// Warte auf Schliessen des Dialogs
						wait_for_dialog_exit = true;

						break;

					default:

						infoPrint("Unbekannter Fehler ist im Server aufgetreten\n");

						guiShowErrorDialog("Unbekannter Fehler", 1);

						wait_for_dialog_exit = true;

						break;
				}

				break;

			case COM_ERR:

				infoPrint("Verbindung zu Server verloren\n");

				guiShowErrorDialog("Verbindung zu Server verloren", 1);

				//guiQuit();

				wait_for_dialog_exit = true;

				break;

			default:
				break;
		}

		// Warteschleife von Listener, wenn Fataler Error oder Spielende
		while (wait_for_dialog_exit == true)
		{
			// Do nothing...
			// Warte auf Client, der das Fenster schliesst
			// und damit den GUI-Thread terminiert
			pthread_join(get_gui_thread(), NULL);

			debugPrint("Ende des Listener-Thread");

			pthread_exit(NULL);
		}
	}

	debugPrint("Unerwartetes Ende Listener-Thread");

	pthread_exit(NULL);

	return NULL;
}
